#include "CPlayer.h"
#include "CPlatform.h"
//#include "DXControl.h"
#include "CCloud.h"
#include "CPlayerIdleState.h"
#include "CPlayerBaseState.h"
#include "CGamePlayState.h"
#include "CGameClass.h"
#include "CTriangle.h"
#include "CRender.h"
#include "CCamera.h"
#include "CSunshine.h"
#include "CSGD_DirectInput.h"
#include "gltools.h"
#include "CSGD_DirectInput.h"
#include "CPlayerBoltedState.h"

// TODO: Ask Mike about this
#include <cmath>

#define PICK_UP_RADIUS 1.0f
#define SCREEN_SCROLL_X 0.3f
#define SCREEN_SCROLL_Y 0.2f

#define WORLD_RADIUS_TWO 27.5
#define WORLD_RADIUS_THREE 46

CPlayer::CPlayer()
{
	WORLD_RADIUS = 9.7f;
	m_pDI = CSGD_DirectInput::GetInstance();

	m_unTimePeriod = GREEK;
	m_pCurrState = 0;
	ChangeState(CPlayerIdleState::GetInstance());
	m_Input = 0; 
	m_fRadius = 1.5f; 
	m_unType = PLAYER; 
	m_fTheta = 0.0f;

	m_Pos = Vec3(0, WORLD_RADIUS, 0);

	m_QUAD.MakeShape(Vec2(-2.5f, -2.5f), Vec2(2.5f, 2.5f), 1);
	//m_QUAD.MakeShape( DXDeviceControl::GetInstance()->GetDevice(), D3DXVECTOR2(0.0f, 1.0f), D3DXVECTOR2( 1.0f, 0.0f), 1 );
	m_fWalkSpeed = 10.0f;
	m_fJumpSpeed = 8.0f;
	m_fPreviousTheta = 0.0f;

	m_bFlipped = false;
	m_bHasSwitched = true;

	//m_TexHandle = CTextureManager::GetInstance()->LoadTexture("img/1.png");
}

void CPlayer::Update(float fElapsed)
{
	CBase::Update(fElapsed);
	CalculateTheta();
	AdjustCamera(fElapsed);

	Vec3 vPlayerToEarth = -m_Pos;
	vPlayerToEarth.Normalize();

	float fDistToCenter = Dot(m_Pos, m_Pos);
	if (fDistToCenter <= (m_fRadius + WORLD_RADIUS) * (m_fRadius + WORLD_RADIUS))
	{
		m_Pos.Normalize();
		m_Pos *= WORLD_RADIUS + m_fRadius;
		m_Vel.Zero();
		if(m_bFalling)
			ChangeState(CPlayerIdleState::GetInstance());
		//m_Pos = Vec3(WORLD_RADIUS + m_fRadius, 0.0f, 0.0f) * Mat4::GetRotateZ(m_fTheta);
	}

	m_pCurrState->Run();
	m_Pos += m_Vel * fElapsed;
	m_Input = 0;

	m_Vel += vPlayerToEarth * fElapsed * 7.0f;

	m_fPreviousTheta = m_fTheta;

	Mat4 rotation;
	Mat4 translation;
	translation.MakeTranslate(m_Pos);
	
	Vec3 camPos = CCamera::GetInstance()->m_CamMat.waxis;
	static Mat4 camMat = CCamera::GetInstance()->m_CamMat;

	CSGD_DirectInput * di = CSGD_DirectInput::GetInstance();

	if(di->KeyDown(DIK_X) && di->KeyDown(DIK_Y))
	{
		Mat4 rotation2;
		rotation.MakeRotateX(-m_fTheta);
		rotation2.MakeRotateY(-m_fTheta);
		CCamera::GetInstance()->m_CamMat = camMat * rotation * rotation2;		
	}
	else if(di->KeyDown(DIK_X))
	{
		rotation.MakeRotateX(-m_fTheta);
		CCamera::GetInstance()->m_CamMat = camMat * rotation;
	}
	else if(di->KeyDown(DIK_Y))
	{
		rotation.MakeRotateY(-m_fTheta);
		CCamera::GetInstance()->m_CamMat = camMat * rotation;
	}
	else
	{
		rotation.MakeRotateZ(-m_fTheta);
		CCamera::GetInstance()->m_CamMat = rotation * translation * camMat;
	}
	
	CRender::GetInstance()->m_fUndoTheta = m_fTheta;

	if (m_unTimePeriod == GREEK && Dot(m_Pos, m_Pos) >= WORLD_RADIUS_TWO * WORLD_RADIUS_TWO)
	{
		ChangeState(CPlayerBoltedState::GetInstance());
		WORLD_RADIUS = WORLD_RADIUS_TWO;
	}
	else if (m_unTimePeriod == MIDEVIL && Dot(m_Pos, m_Pos) >= WORLD_RADIUS_THREE * WORLD_RADIUS_THREE)
	{
		ChangeState(CPlayerBoltedState::GetInstance());
		WORLD_RADIUS = WORLD_RADIUS_THREE;
	}
}

void CPlayer::Render()
{
	//CRender::GetInstance()->DrawTex(m_TexHandle, Vec2(m_Pos.x, m_Pos.y), 0, Vec2(32, 32));
	CRender::GetInstance()->DrawTex(m_pCurrState->GetTextureHandle(), &m_QUAD, Vec2(m_Pos.x, m_Pos.y), 1, m_fTheta, m_bFlipped);

	//DX
	//s_Effect->SetTechnique("FullScreenQuad_Textured");

	//s_Effect->Begin(&a, 0);
	//s_Effect->BeginPass(0);

	//s_Effect->SetTexture( );
	//s_Effect->CommitChanges();
	//s_Device->SetStreamSource( 0, m_QUAD.GetVertexBuffer(), 0, sizeof(CShape::SVertexPT));

	//s_Device->SetTexture( 0, s_pTM->GetTextureByID( m_TextureID ) );

	//s_Device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, m_QUAD.m_numTriangles );
	//s_Effect->EndPass();
	//s_Effect->End();
}

bool CPlayer::CheckCollision(CBase *pBase)
{
	switch(pBase->m_unType)
	{
	case NOODLE:
		{
			if(!((CPlatform*)pBase)->m_bVisible || !((CPlatform*)pBase)->m_bCollidable)
				return false;

			bool bStraddle = false;
			bool bInBounds = false;

			CPlatform *pPlatform = dynamic_cast<CPlatform*>(pBase);

			// Check to see if the player is within the 
			// bounds of the platforms first point and second point
			float fThetaOne = pPlatform->m_fThetaFirst;
			float fThetaTwo = pPlatform->m_fThetaSecond;
			float fInnerLimit = pPlatform->m_fInnerLimit;
			float fOuterLimit = pPlatform->m_fOuterLimit;

			Vec3 vPlayerToWorld = -m_Pos;

			// Player's squared distance from the world's origin
			float fPlayerDistance = m_Pos.Length();

			// Noodle doesn't straddle 2PI/0 line
			if(fThetaOne > fThetaTwo)
			{
				// The player is within the angle bounds...
				if(m_fTheta <= fThetaOne && m_fTheta >= fThetaTwo)
				{
					bInBounds = true;
				}
			}
			// Noodle straddles the 2PI/0 line
			else
			{
				bStraddle = true;
				if((pPlatform->m_fThetaFirst >= m_fTheta && m_fTheta >= 0.0f) || (pPlatform->m_fThetaSecond <= m_fTheta && m_fTheta <= D3DX_PI * 2.0f))
				{
					bInBounds = true;
				}
			}

			if(bInBounds)
			{
				// Player is colliding from either the top of bottom
				if((fPlayerDistance - m_fRadius < fOuterLimit && fPlayerDistance - m_fRadius > fInnerLimit) || 
					(fPlayerDistance + m_fRadius < fOuterLimit && fPlayerDistance + m_fRadius > fInnerLimit))
				{
					vPlayerToWorld.Normalize();

					// Collision as the player is coming down (feet hit the ground)
					if(Dot(vPlayerToWorld, m_Vel) > 0.0f)
					{
						float fDistanceToAdjustBy = fPlayerDistance - fOuterLimit - m_fRadius;
						m_Pos += vPlayerToWorld*fDistanceToAdjustBy;
					}
					// Collision as the player is moving upward (hit his head)
					else
					{
						float fDistanceToAdjustBy = fPlayerDistance - fInnerLimit + m_fRadius;
						m_Pos -= vPlayerToWorld*fDistanceToAdjustBy;
					}
					m_Vel.Zero();
					return true;
				}
			}
			// Check for side collisions
			else if(fPlayerDistance - m_fRadius < fOuterLimit && fPlayerDistance + m_fRadius > fInnerLimit)
			{
				// Test for side collisions
				Vec3 vPlayerOrthogonal(-vPlayerToWorld.y, vPlayerToWorld.x, 0.0f);
				Vec3 vWorldUp( 0.0f, 1.0f, 0.0f );

				vPlayerOrthogonal.Normalize();

				// Left and right max positions
				Vec3 vPlayerMaxOne, vPlayerMaxTwo;
				float fMaxThetaOne, fMaxThetaTwo;

				vPlayerMaxOne = m_Pos - vPlayerOrthogonal * m_fRadius;
				vPlayerMaxTwo = m_Pos + vPlayerOrthogonal * m_fRadius;

				vPlayerMaxOne.Normalize();
				vPlayerMaxTwo.Normalize();

				// Get the angles of the min and max points of the player's collision sphere
				fMaxThetaOne = acos(Dot(vPlayerMaxOne, vWorldUp));
				fMaxThetaTwo = acos(Dot(vPlayerMaxTwo, vWorldUp));

				if(m_fTheta < 0.0f)
				{
					fMaxThetaOne = -fMaxThetaOne;
					fMaxThetaTwo = -fMaxThetaTwo;
				}

				if(!bStraddle)
				{
					// The player is too far from this platform
					if(fMaxThetaOne < fThetaTwo || fMaxThetaTwo > fThetaOne)
						return false;

					// The player collided with a side

					// The player collided with the "left" side of the platform
					if(fMaxThetaTwo < pPlatform->m_fThetaFirst && fMaxThetaOne > pPlatform->m_fThetaFirst)
					{
						float fCollisionTheta = pPlatform->m_fThetaFirst - fMaxThetaTwo;
						m_fTheta += fCollisionTheta;
					}
					// The player collided with the "right" side of the platform
					else
					{
						float fCollisionTheta = fMaxThetaOne - pPlatform->m_fThetaSecond;
						m_fTheta -= fCollisionTheta;
					}

					Mat4 rotation = Mat4::GetRotateZ(-m_fTheta);
					m_Pos = Vec3(0.0f, fPlayerDistance, 0.0f) * rotation;
					m_Pos.z = 0.0f;
					m_Vel.Zero();
					return true;
				}
				else
				{
					// The player collided with a side
					if(((fMaxThetaOne <= fThetaOne && fMaxThetaOne >= 0.0f) || (fMaxThetaOne <= D3DX_PI * 2.0f && fMaxThetaOne >= fThetaTwo)) || 
						((fMaxThetaTwo <= fThetaOne && fMaxThetaTwo >= 0.0f) || (fMaxThetaTwo <= D3DX_PI * 2.0f && fMaxThetaTwo >= fThetaTwo)))
					{
						// The player collided with the "left" side of the platform
						if(Dot(m_Pos, vWorldUp) > 0.0f)
						{
							float fCollisionTheta = pPlatform->m_fThetaFirst - fMaxThetaTwo;
							m_fTheta += fCollisionTheta;
						}
						else
						{
							float fCollisionTheta = fMaxThetaOne - pPlatform->m_fThetaSecond;
							m_fTheta -= fCollisionTheta;
						}
						m_Pos = Vec3(fPlayerDistance, 0.0f, 0.0f) * Mat4::GetRotateZ(m_fTheta);
						m_Vel.Zero();
						return true;
					}
				}
			}
			return false;
		}
		break;
	case WORLD:
		{
			Vec3 vPlayerToWorld = -m_Pos;
			// Player's squared distance from the world's origin
			float fPlayerDistanceSquared = Dot(vPlayerToWorld, vPlayerToWorld);

			if(fPlayerDistanceSquared < WORLD_RADIUS * WORLD_RADIUS)
			{
				m_Pos = Vec3(WORLD_RADIUS + m_fRadius, 0.0f, 0.0f) * Mat4::GetRotateZ(m_fTheta);
				return true;
			}
			return false;
		}
		break;
	case PICK_UP:
		{
			Vec3 vPlayerToPickUp =  m_Pos - pBase->m_Pos;
			
			if(!((CSunshine*)pBase)->m_bPickedUp)
			{
				if(Dot(vPlayerToPickUp, vPlayerToPickUp) < (m_fRadius + PICK_UP_RADIUS) * (m_fRadius + PICK_UP_RADIUS))
				{
					((CSunshine*)pBase)->m_bPickedUp = true;
					((CSunshine*)pBase)->m_fTimer = 0;
					pBase->m_Vel = pBase->m_Pos - m_pCloud->m_Pos;
					pBase->m_Vel.Normalize();
					pBase->m_Vel *= 10.3f;
					
					int nRightOrLeft = rand()%2;
					if(nRightOrLeft)
					{
						pBase->m_Vel += Vec3(-pBase->m_Vel.y, pBase->m_Vel.x, 0.0f) * 1.3f;
					}
					else
					{
						pBase->m_Vel += Vec3(pBase->m_Vel.x, -pBase->m_Vel.y, 0.0f) * 1.3f;
					}
					pBase->m_Vel *= RAND_FLOAT(1.0f, 2.0f);
					
					//////////////////////////////////////////////////////////////////////////
					// TODO FOR ANDY: Add pick up sound hear.
					//
					//////////////////////////////////////////////////////////////////////////
					return true;
				}
			}
			return false;
		}
		break;
	case TRIANGLE:
		{
			CTriangle *pTriangle = dynamic_cast<CTriangle*>(pBase);

			float fThetaTwo = pTriangle->m_fThetaSecond;

			// Player is within the left and right bounds of the triangle
			if(m_fTheta < pTriangle->m_fThetaFirst && m_fTheta > fThetaTwo)
			{
				Vec3 vPlayerToWorld = -m_Pos;

				// Possible collision as the player is moving upward (hit his head)
				if(Dot(vPlayerToWorld, m_Vel) < 0.0f)
				{
					float fPlayerDistanceSquared = Dot(vPlayerToWorld, vPlayerToWorld);

					// Player hit his head on the bottom of the triangle
					if(fPlayerDistanceSquared + m_fRadius*m_fRadius > pTriangle->m_fInnerLimit)
					{
						vPlayerToWorld.Normalize();
						float fDistanceToAdjustBy = sqrt(m_fRadius*m_fRadius - (pTriangle->m_fInnerLimit - fPlayerDistanceSquared));
						m_Pos += vPlayerToWorld*fDistanceToAdjustBy;
						m_Vel.Zero();
						return true;
					}
				}
				// Player is moving down... check under him
				else
				{
					Vec3 vPlayerOrthogonal(-vPlayerToWorld.y, vPlayerToWorld.x, 0.0f);
					Vec3 vWorldUp( 0.0f, 1.0f, 0.0f );

					vPlayerOrthogonal.Normalize();

					// Left and right max positions
					Vec3 vPlayerMaxOne, vPlayerMaxTwo;
					float fMaxThetaOne, fMaxThetaTwo;

					vPlayerMaxOne = m_Pos - vPlayerOrthogonal * m_fRadius;
					vPlayerMaxTwo = m_Pos + vPlayerOrthogonal * m_fRadius;

					// Get the angles of the min and max points of the player's collision sphere
					fMaxThetaOne = acos(Dot(vPlayerMaxOne, vWorldUp));
					fMaxThetaTwo = acos(Dot(vPlayerMaxTwo, vWorldUp));

					float fTriCenterTheta = pTriangle->m_fThetaCenter;
					
					// Within the angular bounds of the "right" half the the triangle peak
					if(fMaxThetaOne >= fThetaTwo && fMaxThetaTwo <= fTriCenterTheta)
					{
						// Find the closest point and check it with this player's circle
						Vec3 vTriRightPoint, vTriCenterPoint;

						vTriCenterPoint = Vec3(sqrt(pTriangle->m_fOuterLimit), 0.0f, 0.0f) * Mat4::GetRotateZ(pTriangle->m_fThetaCenter);
						vTriRightPoint = Vec3(sqrt(pTriangle->m_fInnerLimit), 0.0f, 0.0f) * Mat4::GetRotateZ(pTriangle->m_fThetaSecond);

						Vec3 vTriEdge = vTriCenterPoint - vTriRightPoint;
						Vec3 vTestVect = m_Pos - vTriRightPoint;

						vTriEdge.Normalize();

						float fDotResult = Dot(vTriEdge, vTestVect);

						Vec3 vClosestPoint;

						if(fDotResult < 0.0f)
							vClosestPoint = vTriRightPoint;
						else if(fDotResult * fDotResult > Dot(vTriCenterPoint-vTriRightPoint, vTriCenterPoint-vTriRightPoint))
							vClosestPoint = vTriCenterPoint;
						else
							vClosestPoint = vTriRightPoint + vTriEdge*fDotResult;

						Vec3 vClosestPointToPlayer = m_Pos - vClosestPoint;

						// Player collided
						if(m_fRadius * m_fRadius > Dot(vClosestPointToPlayer, vClosestPointToPlayer))
						{
							m_Pos += vClosestPointToPlayer;
							m_Vel.Zero();
							return true;
						}
					}
					// Within the angular bounds of the "left" half the the triangle peak
					else if(fMaxThetaOne >= fTriCenterTheta && fMaxThetaTwo <= pTriangle->m_fThetaFirst)
					{
						Vec3 vTriCenterPoint, vTriLeftPoint;

						vTriLeftPoint = Vec3(sqrt(pTriangle->m_fInnerLimit), 0.0f, 0.0f) * Mat4::GetRotateZ(pTriangle->m_fThetaFirst);
						vTriCenterPoint = Vec3(sqrt(pTriangle->m_fOuterLimit), 0.0f, 0.0f) * Mat4::GetRotateZ(pTriangle->m_fThetaCenter);

						Vec3 vTriEdge = vTriCenterPoint - vTriLeftPoint;
						Vec3 vTestVect = m_Pos - vTriLeftPoint;

						vTriEdge.Normalize();

						float fDotResult = Dot(vTriEdge, vTestVect);

						Vec3 vClosestPoint;

						if(fDotResult < 0.0f)
							vClosestPoint = vTriLeftPoint;
						else if(fDotResult * fDotResult > Dot(vTriCenterPoint-vTriLeftPoint, vTriCenterPoint-vTriLeftPoint))
							vClosestPoint = vTriCenterPoint;
						else
							vClosestPoint = vTriLeftPoint + vTriEdge*fDotResult;

						Vec3 vClosestPointToPlayer = m_Pos - vClosestPoint;

						// Player collided
						if(m_fRadius * m_fRadius > Dot(vClosestPointToPlayer, vClosestPointToPlayer))
						{
							m_Pos += vClosestPointToPlayer;
							m_Vel.Zero();
							return true;
						}
					}

				}
			}
		}
		break;
	}
	return false;
}

void CPlayer::ChangeState(CPlayerBaseState *pState)
{
	if (m_pCurrState)
		m_pCurrState->Exit();

	m_pCurrState = pState;
	m_pCurrState->Enter(this);
}

void CPlayer::AdjustCamera(float fElapsed)
{
	Mat4 mModelMatrix;
	mModelMatrix.MakeTranslate(m_Pos);

	Mat4 mProjection;
	double matrix[16];
	glGetDoublev(GL_PROJECTION_MATRIX, matrix);
	for(unsigned int i = 0; i < 16; i+=4)
	{
		mProjection.mm[i] = (float)matrix[i];
		mProjection.mm[i+1] = (float)matrix[i+1];
		mProjection.mm[i+2] = (float)matrix[i+2];
		mProjection.mm[i+3] = (float)matrix[i+3];
	}

	Mat4 mViewMatrix = CCamera::GetInstance()->GetCameraMatrix();
	
	// Screen space by multiplying model, view, then projection matrices.
 	Mat4 mScreenSpaceMatrix = mModelMatrix * mViewMatrix * mProjection;

 	float fPlayerSSXCoord = mScreenSpaceMatrix.wx * (float)CGameClass::GetInstance()->GetScreenWidth();
 	float fPlayerSSYCoord = mScreenSpaceMatrix.wy * (float)CGameClass::GetInstance()->GetScreenHeight();
 
 	if(fPlayerSSXCoord < (float)CGameClass::GetInstance()->GetScreenWidth() * SCREEN_SCROLL_X || fPlayerSSXCoord > (float)CGameClass::GetInstance()->GetScreenWidth() * 1.0f - SCREEN_SCROLL_X)
 	{
 		//CCamera::GetInstance()->m_CamMat = Mat4::GetRotateZ(m_fTheta-m_fPreviousTheta) * mModelMatrix;
 	}
 	
 	// If the player gets too high or too low on the screen, apply a camera look-at algorithm
 	if(fPlayerSSYCoord < (float)CGameClass::GetInstance()->GetScreenHeight() * SCREEN_SCROLL_Y || fPlayerSSYCoord > (float)CGameClass::GetInstance()->GetScreenHeight() * 1.0f - SCREEN_SCROLL_Y)
 	{
 		mViewMatrix = CCamera::GetInstance()->GetCameraMatrix();
 		Vec3 vCameraToPlayer(mViewMatrix.wx, mViewMatrix.wy, 0.0f);
 		mViewMatrix.waxis += vCameraToPlayer * fElapsed;
		//CCamera::GetInstance()->m_CamMat = mViewMatrix;
 	}
}

void CPlayer::OnLostDevice(void)
{
	//m_QUAD.OnLost();
	// correct the position
	//m_QUAD.MakeShape( DXDeviceControl::GetInstance()->GetDevice(), D3DXVECTOR2(0.0f, 1.0f), D3DXVECTOR2( 1.0f, 0.0f), 1);
}